using System;
using System.Collections.Generic;
using System.Text;
using ElteIk.RoboSoccer.Core.Physics;
using System.Collections;

namespace ElteIk.RoboSoccer.Core
{
	/// <summary>
	/// Soccer Game class.
	/// Contains all the logic needed for playing a soccer game.
	/// </summary>
    public class SoccerGame : Game
    {
        #region Inner classes

        [Flags]
        public enum Teams
        {
            None = 0,
            LeftTeam = 1,
            RightTeam = 2
        }

        protected enum GameStates
        {
            /// <summary>
            /// Stopped or not started.
            /// </summary>
            NotStarted,

            /// <summary>
            /// Players and the ball have been placed to their positions.
            /// </summary>
            PlayersPlaced,

            /// <summary>
            /// The game is running, so both teams can score.
            /// </summary>
            Running,

            /// <summary>
            /// Goal was shot so neither team can score.
            /// </summary>
            GoalWasShot
        }

        #endregion

        #region Lifecycle

        public SoccerGame()
			: base()
		{
            ball.Radius = 4.27f/2;
		}

		#endregion

		#region Fields

		private List<IPlayer> players = new List<IPlayer>(16);		
		private Ball ball = new Ball();
		private SoccerField field = new SoccerField(150.0f, 130.0f, 40.0f, 10.0f, 7.0f);

        protected GameStates gameState = GameStates.NotStarted;

        private SoccerTeam leftSoccerTeam;
        private SoccerTeam rightSoccerTeam;

        private int leftTeamScore;
        private int rightTeamScore;

		#endregion

		#region Properties

		public SoccerTeam LeftTeam
		{
			get { return leftSoccerTeam; }
		}

        public SoccerTeam RightTeam
        {
            get { return rightSoccerTeam; }
        }

        public int LeftTeamScore
        {
            get { return leftTeamScore; }
        }

        public int RightTeamScore
        {
            get { return rightTeamScore; }
        }

		public List<IPlayer> Players
		{
			get
			{
				return players;
			}
		}

		public Ball Ball
		{
			get
			{
				return ball;
			}
		}

		public SoccerField Field
		{
			get
			{
				return field;
			}
		}

		#endregion

		#region Game controlling

        /// <summary>
        /// Adds a player to the game and to the physical engine as well.
        /// </summary>
        /// <param name="player"></param>
		public virtual void AddPlayer(IPlayer player)
		{
			players.Add(player);
            physicsEngine.Add(player);
		}

        /// <summary>
        /// Resets the physics engine and fills it up with the objects waiting for materialization in the game logic space.
        /// </summary>
        protected virtual void InitializeWorld()
        {
            physicsEngine.AddWorld(field);
            physicsEngine.AddCircle(ball);
            leftTeamScore = 0;
            rightTeamScore = 0;

            //DoPlacements();
        }

        public override void Initialize()
        {
            base.Initialize();

            InitializeWorld();
        }

        /// <summary>
        /// Starts the game (on a separate thread)
        /// </summary>
		public override void Start()
		{
            Reset();
            DoPlacements();
            gameState = GameStates.Running;
            base.Start();
        }

        /// <summary>
        /// Runs the game.
        /// </summary>
        /// <param name="timeLimit"></param>
        public override void Run(decimal timeLimit)
        {            
            gameState = GameStates.Running;
            base.Run(timeLimit);
        }

        /// <summary>
        /// Sets the two teams, adds the players of them.
        /// </summary>
        /// <param name="left"></param>
        /// <param name="right"></param>
        protected virtual void Setup(SoccerTeam left, SoccerTeam right)
        {
            players.Clear();
            leftSoccerTeam = left;
            rightSoccerTeam = right;
            foreach (IPlayer player in left.Players)
            {
                AddPlayer(player);
            }

            foreach (IPlayer player in right.Players)
            {
                AddPlayer(player);
            }
        }

		#endregion

        #region Game logic

        /// <summary>
        /// Steps the game model.
        /// </summary>
        /// <param name="f"></param>
        public override void Step(float f)
        {
            if (newRoundRequested)
            {
                DoPlacements();
                gameState = GameStates.Running;
                newRoundRequested = false;
                OnRoundStarted();
            }
            base.Step(f);
            CheckBallHits();
            CheckGoals();
            if ( physicsEngine.WasCollision() )
                OnCollision();
        }

        private bool newRoundRequested = false;

        /// <summary>
        /// Tells the game to start a new round.
        /// </summary>
        public virtual void NewRound()
        {
            newRoundRequested = true;
        }

        /// <summary>
        /// Places the players and the ball at the right position.
        /// </summary>
        public virtual void DoPlacements()
        {
            if (gameState == GameStates.PlayersPlaced)
                return;
            lock (this)
            {
                // Place goalkeepers first
                Vector2D distanceFromGoalVector = new Vector2D(15f, 0f);

                Place(leftSoccerTeam.Players[0], field.Goals[0].Center + distanceFromGoalVector, Vector2D.X_Axis);
                Place(rightSoccerTeam.Players[0], field.Goals[1].Center - distanceFromGoalVector, Vector2D.X_Axis.Negated);

                // Place the left players evenly on the left half of the field
                Place(leftSoccerTeam.Players[1], field.Center - new Vector2D(field.Width / 4, field.Height / 4), Vector2D.X_Axis);
                Place(leftSoccerTeam.Players[2], field.Center - new Vector2D(field.Width / 4, -field.Height / 4), Vector2D.X_Axis);

                // Place the right players evenly on the right half of the field
                Place(rightSoccerTeam.Players[1], field.Center + new Vector2D(field.Width / 4, field.Height / 4), Vector2D.X_Axis.Negated);
                Place(rightSoccerTeam.Players[2], field.Center + new Vector2D(field.Width / 4, -field.Height / 4), Vector2D.X_Axis.Negated);

                Place(ball, field.Center, Vector2D.Y_Axis);

                // Stop each object
                foreach (IPlayer player in players)
                {
                    player.Velocity = Vector2D.NullVector;
                }

                ball.Velocity = Vector2D.NullVector;

                physicsEngine.SynchronizeWithGame();
                gameState = GameStates.PlayersPlaced;
            }
        }

        public void Place(IPhysicalObject physicalObject, Vector2D position, Vector2D heading)
        {
            physicalObject.Position = position;
            physicalObject.Heading = heading;
        }

        public void Place(IPhysicalObject physicalObject, float posX, float posY, float headX, float headY)
        {
            Place(physicalObject, new Vector2D(posX, posY), new Vector2D(headX, headY));
        }

        /// <summary>
        /// The player who last touched the ball.
        /// </summary>
        private IPlayer grazer = null;

        /// <summary>
        /// Checks if goal was shot and handles it.
        /// </summary>
        protected void CheckGoals()
        {
            if (gameState != GameStates.Running)
                return;

            Teams scoringTeam = Teams.None;

            // Does any of the goals contain the ball?
            if (field.Goals[0].Contains(ball))
            {
                scoringTeam = Teams.RightTeam;
                rightTeamScore++;
            }
            else if (field.Goals[1].Contains(ball))
            {
                scoringTeam = Teams.LeftTeam;
                leftTeamScore++;
            }

            // If yes, raise the event
            if (scoringTeam != Teams.None)
            {
                gameState = GameStates.GoalWasShot;
                OnGoalShoot(grazer, scoringTeam);
            }
        }

        /// <summary>
        /// Checks if one of the players have hit the ball.
        /// In that case that player will be assigned to the field "grazer".
        /// </summary>
        private void CheckBallHits()
        {
            IEnumerable<IPhysicalObject> colliders = physicsEngine.GetLastColliders(ball);
            foreach (IPhysicalObject physicalObject in colliders)
            {
                if (physicalObject is IPlayer)
                {
                    grazer = physicalObject as IPlayer;
                }
            }
        }

        #endregion

        #region Event Specifications

        public class PlayerEventArgs : EventArgs
		{
			private IPlayer player;
			public IPlayer Player
			{
				get { return player; }
			}

			public PlayerEventArgs(IPlayer player)
			{
				this.player = player;
			}
		}

        public class GoalEventArgs : PlayerEventArgs
        {
            private Teams scoringTeam;
            public Teams ScoringTeam
            {
                get { return scoringTeam; }
            }

            public GoalEventArgs(IPlayer player, Teams scoringTeam)
                : base(player)
            {
                this.scoringTeam = scoringTeam;
            }
        }

		#endregion

		#region Events

		public event EventHandler GamePause;
		public event EventHandler<PlayerEventArgs> PlayerHitTheBall;
		public event EventHandler<GoalEventArgs> GoalShoot;
		public event EventHandler EndRound;
		public event EventHandler RoundStart;
        public event EventHandler Collision;

		protected void OnGamePaused()
		{
			if (GamePause != null)
				GamePause(this, new EventArgs());
		}

		protected void OnEndRound()
		{
			if (EndRound != null)
				EndRound(this, new EventArgs());
		}

		protected void OnRoundStarted()
		{
			if (RoundStart != null)
				RoundStart(this, new EventArgs());
		}

		protected void OnPlayerHitTheBall(IPlayer player)
		{
			if (PlayerHitTheBall != null)
				PlayerHitTheBall(this, new PlayerEventArgs(player));
		}

		protected void OnGoalShoot(IPlayer player, Teams scoringTeam)
		{
			if (GoalShoot != null)
				GoalShoot(this, new GoalEventArgs(player, scoringTeam));
		}

        protected void OnCollision()
        {
            if (Collision != null)
                Collision(this, new EventArgs());
        }

		#endregion
    }
}
